package pl.agh.convexhulltutor.gui;

import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JTextField;
import javax.swing.KeyStroke;

import pl.agh.convexhulltutor.algorithms.Algorithm;
import pl.agh.convexhulltutor.algorithms.BruteForceAlgorithm;
import pl.agh.convexhulltutor.algorithms.GrahamAlgorithm;
import pl.agh.convexhulltutor.algorithms.JarvisAlgorithm;
import pl.agh.convexhulltutor.algorithms.QuickhullAlgorithm;
import pl.agh.convexhulltutor.algorithms.UpperAndLowerConvexHullAlgorithm;
import pl.agh.convexhulltutor.structures.Line;
import pl.agh.convexhulltutor.structures.Point;

@SuppressWarnings("serial")
public class ConvexHullFrame extends JFrame {
	private static final int FRAME_WIDTH = 800;
	private static final int FRAME_HEIGHT = 600;
	private static final String FRAME_LABEL = "Convex Hull Tutor";

	private static boolean algorithmRunning = false;

	private DrawPanel drawPanel = new DrawPanel();

	private Algorithm currentAlgorithm;

	private JMenu programMenu = new JMenu("Program");
	private JMenu algorithmsMenu = new JMenu("Algorithms");
	private JMenu actionsMenu = new JMenu("Actions");

	private JMenuItem generatePointsItem = new JMenuItem("Generate points");
	private JMenuItem clearPointsItem = new JMenuItem("Clear points");
	private JMenuItem exitItem = new JMenuItem("Exit");

	private JMenuItem bruteForceAlgorithmItem = new JMenuItem("Brute-force");
	private JMenuItem upperAndLowerConvexHullAlgorithmItem = new JMenuItem(
			"Upper and Lower Convex Hull");
	private JMenuItem jarvisAlgorithmItem = new JMenuItem("Jarvis");
	private JMenuItem grahamAlgorithmItem = new JMenuItem("Graham");
	private JMenuItem quickhullAlgorithmItem = new JMenuItem("Quickhull");

	private JMenuItem stopAlgorithmItem = new JMenuItem("Stop algorithm");
	private JMenuItem previousStepItem = new JMenuItem("Previous step");
	private JMenuItem nextStepItem = new JMenuItem("Next step");

	public ConvexHullFrame() {
		setTitle(FRAME_LABEL);
		setSize(FRAME_WIDTH, FRAME_HEIGHT);
		setResizable(false);
		setLocationRelativeTo(null);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		setContentPane(drawPanel);
		createMenuBar();

		setVisible(true);
	}

	private void createMenuBar() {
		JMenuBar toolbar = new JMenuBar();

		int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();

		setAlgorithmRunning(algorithmRunning);

		generatePointsItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				final JDialog pointsGenerationDialog = new JDialog();

				final JLabel label = new JLabel("Number of points");
				final JTextField textField = new JTextField("10");
				final JButton generateButton = new JButton("Generate");
				final JButton cancelButton = new JButton("Cancel");

				final GridLayout gridLayout = new GridLayout(2, 2);

				gridLayout.setHgap(7);
				gridLayout.setVgap(3);

				pointsGenerationDialog.setLayout(gridLayout);
				pointsGenerationDialog.setModal(true);
				pointsGenerationDialog.setTitle("Generate points");
				pointsGenerationDialog.setSize(250, 75);
				pointsGenerationDialog.setLocationRelativeTo(null);

				label.setHorizontalAlignment(JLabel.CENTER);
				textField.setHorizontalAlignment(JTextField.CENTER);

				cancelButton.addActionListener(new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent e) {
						pointsGenerationDialog.dispose();
					}
				});

				generateButton.addActionListener(new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent ae) {
						try {
							int numberOfPoints = Integer.parseInt(textField
									.getText());
							Random random = new Random();

							if (numberOfPoints <= 0) {
								throw new NumberFormatException();
							}
							pointsGenerationDialog.dispose();
							for (int i = 0; i < numberOfPoints; i++) {
								drawPanel.addPoint(new Point(
										Math.abs(random.nextInt())
												% (drawPanel.getWidth() - 2 * DrawPanel.DRAW_PANEL_MARGIN)
												+ DrawPanel.DRAW_PANEL_MARGIN,
										Math.abs(random.nextInt())
												% (drawPanel.getHeight() - 2 * DrawPanel.DRAW_PANEL_MARGIN)
												+ DrawPanel.DRAW_PANEL_MARGIN));
							}
							drawPanel.repaint();
						} catch (NumberFormatException e) {
							pointsGenerationDialog.dispose();
							MessageBox
									.warn("You must type positive decimal value");
						}
					}
				});

				pointsGenerationDialog.add(label);
				pointsGenerationDialog.add(textField);
				pointsGenerationDialog.add(cancelButton);
				pointsGenerationDialog.add(generateButton);

				pointsGenerationDialog.setVisible(true);
			}
		});
		clearPointsItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				drawPanel.setPoints(new ArrayList<Point>());
				drawPanel.setLines(new ArrayList<Line>());
				drawPanel.repaint();
			}
		});
		exitItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		});

		bruteForceAlgorithmItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				runAlgorithm(new BruteForceAlgorithm(drawPanel));
			}
		});
		upperAndLowerConvexHullAlgorithmItem
				.addActionListener(new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent e) {
						runAlgorithm(new UpperAndLowerConvexHullAlgorithm(
								drawPanel));
					}
				});
		jarvisAlgorithmItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				runAlgorithm(new JarvisAlgorithm(drawPanel));
			}
		});
		grahamAlgorithmItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				runAlgorithm(new GrahamAlgorithm(drawPanel));
			}
		});
		quickhullAlgorithmItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				runAlgorithm(new QuickhullAlgorithm(drawPanel));
			}
		});

		previousStepItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				currentAlgorithm.previousStep();
			}
		});
		nextStepItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				currentAlgorithm.nextStep();
			}
		});
		stopAlgorithmItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				currentAlgorithm = null;
				drawPanel.restart();
				algorithmRunning = false;
				setAlgorithmRunning(false);
			}
		});

		generatePointsItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G,
				mask));
		clearPointsItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C,
				mask));
		exitItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, mask));

		previousStepItem.setAccelerator(KeyStroke.getKeyStroke("LEFT"));
		nextStepItem.setAccelerator(KeyStroke.getKeyStroke("RIGHT"));
		stopAlgorithmItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
				mask));

		bruteForceAlgorithmItem.setAccelerator(KeyStroke.getKeyStroke(
				KeyEvent.VK_1, mask));
		upperAndLowerConvexHullAlgorithmItem.setAccelerator(KeyStroke
				.getKeyStroke(KeyEvent.VK_2, mask));
		jarvisAlgorithmItem.setAccelerator(KeyStroke.getKeyStroke(
				KeyEvent.VK_3, mask));
		grahamAlgorithmItem.setAccelerator(KeyStroke.getKeyStroke(
				KeyEvent.VK_4, mask));
		quickhullAlgorithmItem.setAccelerator(KeyStroke.getKeyStroke(
				KeyEvent.VK_5, mask));

		programMenu.add(generatePointsItem);
		programMenu.add(clearPointsItem);
		programMenu.addSeparator();
		programMenu.add(exitItem);

		algorithmsMenu.add(bruteForceAlgorithmItem);
		algorithmsMenu.add(upperAndLowerConvexHullAlgorithmItem);
		algorithmsMenu.add(jarvisAlgorithmItem);
		algorithmsMenu.add(grahamAlgorithmItem);
		algorithmsMenu.add(quickhullAlgorithmItem);

		actionsMenu.add(previousStepItem);
		actionsMenu.add(nextStepItem);
		actionsMenu.addSeparator();
		actionsMenu.add(stopAlgorithmItem);

		toolbar.add(programMenu);
		toolbar.add(algorithmsMenu);
		toolbar.add(actionsMenu);

		setJMenuBar(toolbar);
	}

	private void setAlgorithmRunning(boolean algoritmRunning) {
		algorithmsMenu.setEnabled(!algoritmRunning);
		generatePointsItem.setEnabled(!algoritmRunning);
		clearPointsItem.setEnabled(!algoritmRunning);

		stopAlgorithmItem.setEnabled(algoritmRunning);
		previousStepItem.setEnabled(algoritmRunning);
		nextStepItem.setEnabled(algoritmRunning);
	}

	private void runAlgorithm(Algorithm algorithm) {
		drawPanel.restart();
		currentAlgorithm = algorithm;
		if (drawPanel.getPoints().size() < 3) {
			MessageBox
					.warn("You have to specify at least three points to run algorithm");
		} else {
			algorithmRunning = true;
			setAlgorithmRunning(true);
			currentAlgorithm.nextStep();
		}
	}

	public static boolean isAlgoritmRunning() {
		return algorithmRunning;
	}

	public static void main(String[] args) {
		new ConvexHullFrame();
	}
}
